//
/// 含锁量很高
// Created by xietianqi on 2025/1/12.
//


#include "include/CentralCache.h"
#include "../page_cache/include/PageCache.h"

CentralCache CentralCache::_cacheInstance;

/**
   * 从central cache取对象使用参数来接收目标值开始从span里面切除一个小块组成的链表
   * @param start 返回链表头
   * @param end   返回链表尾
   * @param batchNum 希望拿到一批小块的个数
   * @param size 一个小块的大小
   * @return 实际拿到了一小批的个数
   */
size_t CentralCache::FetchRangeObj(void *&start, void *&end, size_t batchNum, size_t size) {
    // 计算要哪个筒的内存
    size_t index = SizeUtils::Index(size);
    // 智能指针加锁
//    std::unique_lock<std::mutex> lock(_spanLists[index].getMtx());
    /// 加筒锁
    _spanLists[index]._mtx.lock();

    //// 从对应筒中找到我想要的不为空的span
    Span *span = GetOneSpan(_spanLists[index], size);
    assert(span);
    assert(span->_freeList);
    //开始节点等于这个span->_freeList的头,end从头往后走batchNum-1步数既可以拿到batchNum个节点的链表
    start = end = span->_freeList;
    size_t actualNum = 1; //不为空默认有一个
    //// 走batchNum-1步 && NextObj(end)!= nullptr;防止一个span拿不出这么多个需要的节点导致崩溃
    for (int i = 0; i < batchNum - 1 && NextObj(end) != nullptr; ++i) {
        // bug3 end = NextObj(span->_freeList);
        end = NextObj(end);
        // 每获取一个加一
        actualNum++;
    }

    span->_freeList = NextObj(end);  // 把头指针指向链表被拿走后的剩余部分
    NextObj(end) = nullptr;//给我自己截出来的小链表做一个收尾
    //这个地方需要对本span扣出去的自由链表的节点数进行计数以供归还使用
    span->_useCount += actualNum;

    _spanLists[index]._mtx.unlock();
    return actualNum;
}

/**
 * 获取一个非空的span
 * @param list span 一个筒下的Span双向链表
 * @param size 对齐数的大小
 * @return 一个span
 */
Span *CentralCache::GetOneSpan(SpanList &list, size_t size) {
    // 如果span有就拿
    Span *it = list.Begin();
    while (it != list.End()) {
        if (it->_freeList != nullptr) {
            //说明span下面挂的有对象
            return it;
        }
        // 迭代器往下走一步
        it = it->_next;
    }
    /// 先解开central cache的筒锁如果其他线程'**释放资源**'回来不会阻塞
    list._mtx.unlock();
    /// 没有空闲的span到下一层page cache要,切好给你拿来一个大块空间然后你算出地址切完了之后,对其的自由链表进行封装
    /// 需要加锁控制
    PageCache::GetInstance()->_pageMtx.lock();
    // 新切了一个出来
    Span *span = PageCache::GetInstance()->NewSpan(SizeUtils::NumMovePage(size));


    // 置span使用标记为true正在使用
    span->_isUse= true;
    // 直接设置这个对齐数的大小,方便删
    span->_objectSize = size;
    PageCache::GetInstance()->_pageMtx.unlock();
    /// 对获取span切分不需要加锁,因为其他线程拿不到这个span还在我自己这里还没有挂到list上
    //从span拿到id乘以8k后强转成char*算出起始地址
    char *start = reinterpret_cast<char *>(span->_pageId << PAGE_SHIFT);
    // 计算大块内存的的大小页数乘以8k
    size_t bytes = span->_n << PAGE_SHIFT;
    //终止的地址
    char *end = start + bytes;
    //切割成size大小的n个小块尾插法链接起来 保证内存连续性
    //先切一个做头
    span->_freeList = start;
    start += size;
    //定义一个已经插入进去span里的小链表的尾部tail
    void *tail = span->_freeList;
    while (start < end) {
        NextObj(tail) = start;
        tail = NextObj(tail); // 维护span里链表的尾巴往后走
        start += size; //维护待切割的内存块的start往后加
    }
    NextObj(tail) = nullptr;   //bug4没置空导致实际的节点数量和计数不符
    /// 给spanlist头插一个span 这里需要恢复加锁因为访问了hash筒
    list._mtx.lock();
    list.PushFront(span);
    return span;
}

/**
* 归还ThreadCache的一部分自由链表给spans!!!不是span
* size要算筒
 * 我怎么知道他是那个span呢?
* @param start 自由链表起点
* @param size 一个节点的大小(字节)
*/
void CentralCache::ReleaseListToSpans(void *start, size_t size) {
    // 计算桶备用
    size_t index = SizeUtils::Index(size);
    ///加锁
    _spanLists[index]._mtx.lock();
    while(start){
        // 保存一开始待归还指针的第二个节点
        void* next = NextObj(start);
        // 拿到要归还的span --读没有被锁住
        Span* span = PageCache::GetInstance()->MapObjectToSpan(start);
        // 把小自由链表头插到span里
        // 先把list链到start后面再把start 给了_freeList
        NextObj(start) = span->_freeList;
        span->_freeList =start;
        span->_useCount--;
        if(span->_useCount==0){
            /**
             * 说明span的所有切出去的小块内存都回来了
             * 这个span可以向上pagecache归还由pagecache做前后页合并
             * /// 但不需要把一个span下面的小块归并因为知道整块的起始地址终止地址还是可以继续切的不需要
             */
            //首先把span erase掉
            _spanLists[index].Erase(span);
            span->_freeList = nullptr; // 直接置空
            span->_next = nullptr;
            span->_prev = nullptr;
            // 页号和页数不能动
            /// 还给下一层
            /// 先筒解锁 因为释放给pagecache的时候可以用pagecache自己的锁这个筒的资源没必要一直占用可以让别人继续归还回来
            _spanLists[index]._mtx.unlock();

            /// 加大锁-----
            PageCache::GetInstance()->_pageMtx.lock();
            //没有置isUse为false这个好像是不需要了,等合并完了会置为false的
            PageCache::GetInstance()->ReleaseSpanToPageCache(span);
            /// 解大锁-----
            PageCache::GetInstance()->_pageMtx.unlock();

            /// 再加回来筒锁
            _spanLists[index]._mtx.lock();
        }
        // 指向待释放链表的start指针往下挪
        start = next;
    }
    ///解锁
    _spanLists[index]._mtx.unlock();
}
